package de.tuberlin.dima.minidb.parser;


import java.util.ArrayList;
import java.util.Iterator;


/**
 * A parse tree node representing a generic predicate. It represents predicates for both the
 * WHERE clause and the HAVING clause. Predicate can occur in the form
 * <ul>
 *   <li><i>tab.col op literal</i> (WHERE clause only) </li>
 *   <li><i>tabA.colC op tabB.ColD</i> (WHERE clause only) </li>
 *   <li><i>outputCol(=alias col) op literal</i> (HAVING clause only) </li>
 * </ul>
 * 
 * The predicate has a type that declares which of the above types this predicate has. It
 * will automatically determine the type when the left- and right hand field get set.
 * 
 * @author Stephan Ewen (sewen@cs.tu-berlin.de)
 */
public class Predicate implements ParseTreeNode
{
	/**
	 * Enumeration that defines the type of a predicate.
	 */
	public enum PredicateType
	{
		UNDETERMINED, COLUMN_COLUMN, COLUMN_LITERAL, ALIASCOLUMN_LITERAL;
	}
	
	
	/**
	 * Enumeration to identify the type of predicate operator.
	 */
	public enum Operator
	{
		UNDETERMINED("?"),
		EQUAL("="), NOT_EQUAL("<>"),
		SMALLER("<"), SMALLER_OR_EQUAL("<="),
		GREATER(">"), GREATER_OR_EQUAL(">=");
		
		/**
		 * The symbol of this operator.
		 */
		private String symbol;
		
		/**
		 * Creates an operator with the given symbol.
		 * 
		 * @param symbol The symbol of the operator.
		 */
		private Operator(String symbol)
		{
			this.symbol = symbol;
		}
		
		/**
		 * Gets this operator's symbol.
		 * 
		 * @return The operator symbol.
		 */
		public String getSymbol()
		{
			return this.symbol;
		}
	}
	
	
	/**
	 * This predicate's type.
	 */
	protected PredicateType type;
	
	/**
	 * This predicate's operator.
	 */
	protected Operator op;
	
	/**
	 * The column on the left hand side.
	 */
	protected Column leftHandCol;
	
	/**
	 * The alias column on the left hand side. 
	 */
	protected OutputColumn leftHandAliasColumn;
	
	/**
	 * The literal on the right hand side.
	 */
	protected Literal rightHandLiteral;
	
	/**
	 * The column on the right hand side.
	 */
	protected Column rightHandColumn;
	
	
	
	/**
	 * Creates a plain empty predicate with yet undetermined type.
	 */
	public Predicate() {
		type = PredicateType.UNDETERMINED;
		op = Operator.UNDETERMINED;
	}
	
	
	/**
	 * Sets the left hand side to the given column. Erases previously set fields
	 * that would be inconsistent with this left hand side (previous left hand alias column)
	 * and tries to determine the type.
	 * 
	 * @param col The column forming the left hand side.
	 */
	public void setLeftHandSide(Column col)
	{
		leftHandAliasColumn = null;
		leftHandCol = col;
		determineType();
	}
	
	/**
	 * Sets the left hand side to the given output column. The output column can either be directly
	 * the column that is child of the select clause or a new OutputColumn object whos's alias name
	 * is only interpreted.
	 * Erases previously set fields that would be inconsistent with this left hand side (previous left 
	 * hand column or right hand column) and tries to determine the type.
	 * 
	 * @param col The alias column forming the left hand side.
	 */
	public void setLeftHandSide(OutputColumn col)
	{
		leftHandAliasColumn = col;
		leftHandCol = null;
		rightHandColumn = null;
		determineType();
	}
	
	/**
	 * Sets the operator for this predicate.
	 * 
	 * @param op The predicate operator.
	 */
	public void setOperator(Operator op)
	{
		this.op = op;
	}
	
	/**
	 * Sets the right hand side to the given column. Erases previously
	 * set fields that would be inconsistent with this right hand side (previous right 
	 * hand column or left hand alias column) and tries to determine the type.
	 * 
	 * @param col The column forming the right hand side.
	 */
	public void setRighHandSide(Column col)
	{
		rightHandColumn = col;
		rightHandLiteral = null;
		leftHandAliasColumn = null;
		determineType();
	}
	
	/**
	 * Sets the right hand side to the given literal. Erases previously
	 * set fields that would be inconsistent with this right hand side (previous right 
	 * hand column) and tries to determine the type.
	 * 
	 * @param literal The literal forming the right hand side.
	 */
	public void setRighHandSide(Literal literal)
	{
		rightHandLiteral = literal;
		rightHandColumn = null;
		determineType();
	}
	
	/**
	 * Tries to determine the predicate type from the fields that have been set.
	 */
	private void determineType()
	{
		if (leftHandAliasColumn != null && leftHandCol == null) {
			type = PredicateType.ALIASCOLUMN_LITERAL;
		}
		else if (leftHandCol != null && leftHandAliasColumn == null) {
			if (rightHandColumn != null && rightHandLiteral == null) {
				type = PredicateType.COLUMN_COLUMN;
			} 
			else if (rightHandColumn == null && rightHandLiteral != null) {
				type = PredicateType.COLUMN_LITERAL;
			}
			else {
				type = PredicateType.UNDETERMINED;
			}
		}
		else {
			type = PredicateType.UNDETERMINED;
		}
	}
	
	/**
	 * Checks whether the predicate is completely formed with all of its children.
	 * 
	 * @return true, is the predicate is complete, false otherwise.
	 */
	public boolean isComplete()
	{
		return ( (  type == PredicateType.ALIASCOLUMN_LITERAL &&
				    leftHandAliasColumn != null &&
				    rightHandLiteral != null ) ||
				 (  type == PredicateType.COLUMN_COLUMN &&
				    leftHandCol != null &&
				    rightHandColumn != null ) ||
				 (  type == PredicateType.COLUMN_LITERAL &&
				    leftHandCol != null &&
				    rightHandColumn != null ) ) &&
			   op != Operator.UNDETERMINED;
	}
	

	/* (non-Javadoc)
	 * @see de.tuberlin.dima.minidb.parser.ParseTreeNode#getChildren()
	 */
	public Iterator<? extends ParseTreeNode> getChildren()
	{
		ArrayList<ParseTreeNode> list = new ArrayList<ParseTreeNode>();
		
		// add left hand side
		if (leftHandAliasColumn != null) {
			list.add(leftHandAliasColumn);
		}
		else if (leftHandCol != null) {
			list.add(leftHandCol);
		}
		
		// add right hand side
		if (rightHandColumn != null) {
			list.add(rightHandColumn);
		}
		
		return list.iterator();
	}

	/* (non-Javadoc)
	 * @see de.tuberlin.dima.minidb.parser.ParseTreeNode#getNodeContents()
	 */
	public String getNodeContents()
	{
		StringBuilder bld = new StringBuilder();
		
		ParseTreeNode left = leftHandAliasColumn != null ? leftHandAliasColumn : leftHandCol;
		ParseTreeNode right = rightHandColumn != null ? rightHandColumn : rightHandLiteral;
		
		bld.append(left.getNodeContents());
		bld.append(' ').append(op.getSymbol()).append(' ');
		bld.append(right.getNodeContents());
		
		return bld.toString();
	}

	/* (non-Javadoc)
	 * @see de.tuberlin.dima.minidb.parser.ParseTreeNode#getNodeName()
	 */
	public String getNodeName()
	{
		return "PREDICATE";
	}

	/* (non-Javadoc)
	 * @see de.tuberlin.dima.minidb.parser.ParseTreeNode#getNumberOfChildren()
	 */
	public int getNumberOfChildren()
	{
		int children = 0;
		
		children += leftHandAliasColumn == null ? 0 : 1;
		children += leftHandCol == null ? 0 : 1;
		children += rightHandColumn == null ? 0 : 1;
		
		return children;
	}

	/**
	 * Gets this predicate's type.
	 * 
	 * @return The predicate type.
	 */
	public PredicateType getType()
	{
		return type;
	}

	/**
	 * Gets this predicate's operator.
	 * 
	 * @return The predicate operator.
	 */
	public Operator getOp()
	{
		return op;
	}

	/**
	 * Gets the column forming the left hand side.
	 * 
	 * @return The left hand side column, or null, if not set or used.
	 */
	public Column getLeftHandCol()
	{
		return leftHandCol;
	}

	/**
	 * Gets the column alias forming the left hand side.
	 * 
	 * @return The left hand side column alias, or null, if not set or used.
	 */
	public OutputColumn getLeftHandAliasColumn()
	{
		return leftHandAliasColumn;
	}

	/**
	 * Gets the literal forming the right hand side.
	 * 
	 * @return The right hand side literal, or null, if not set or used.
	 */
	public Literal getRightHandLiteral()
	{
		return rightHandLiteral;
	}

	/**
	 * Gets the column forming the right hand side.
	 * 
	 * @return The right hand side column, or null, if not set or used.
	 */
	public Column getRightHandColumn()
	{
		return rightHandColumn;
	}


	/* (non-Javadoc)
	 * @see de.tuberlin.dima.minidb.parser.ParseTreeNode#isIdenticalTo(de.tuberlin.dima.minidb.parser.ParseTreeNode)
	 */
	public boolean isIdenticalTo(ParseTreeNode node)
	{
		if (node != null && node instanceof Predicate)
		{
			Predicate other = (Predicate) node;
			
			return ( (leftHandCol == null && other.leftHandCol == null) ||
					 (leftHandCol != null && other.leftHandCol != null &&
					  leftHandCol.isIdenticalTo(other.leftHandCol))
				   ) && (
					 (leftHandAliasColumn == null && other.leftHandAliasColumn == null) ||
					 (leftHandAliasColumn != null && other.leftHandAliasColumn != null &&
					  leftHandAliasColumn.isIdenticalTo(other.leftHandAliasColumn))
				   ) && (
				     (rightHandLiteral == null && other.rightHandLiteral == null) ||
					 (rightHandLiteral != null && other.rightHandLiteral != null &&
					  rightHandLiteral.isIdenticalTo(other.rightHandLiteral))
				   ) && (
				     (rightHandColumn == null && other.rightHandColumn == null) ||
					 (rightHandColumn != null && other.rightHandColumn != null &&
					  rightHandColumn.isIdenticalTo(other.rightHandColumn))
				   ) && (op == other.op);
		}
		
		return false;
	}
}
